﻿namespace Amazon.Checkout.Services
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    using JetBrains.Annotations;

    using Models;

    using Orchard;
    using Orchard.Data;
    using Orchard.Localization;
    using Orchard.Logging;
    using Orchard.Security;
    using Orchard.UI.Notify;

    [UsedImplicitly]
    public class CheckoutByAmazonService : ICheckoutByAmazonService
    {
        private readonly IRepository<PromotionRecord> promotionsRepository;
        private readonly IRepository<TaxRuleRecord> taxesRepository;
        private readonly IRepository<TaxTableRecord> taxTablesRepository;
        private readonly IRepository<ShippingRecord> shippingRepository;
        private readonly INotifier notifier;
        private readonly IAuthorizationService authorizationService;
        private readonly IOrchardServices orchardServices;

        public CheckoutByAmazonService(
                                IRepository<PromotionRecord> promotionsRepository,
                                IRepository<TaxRuleRecord> taxesRepository,
                                IRepository<TaxTableRecord> taxTablesRepository,
                                IRepository<ShippingRecord> shippingRepository,
                                INotifier notifier,
                                IAuthorizationService authorizationService,
                                IOrchardServices orchardServices)
        {
            this.promotionsRepository = promotionsRepository;
            this.taxesRepository = taxesRepository;
            this.taxTablesRepository = taxTablesRepository;
            this.shippingRepository = shippingRepository;

            this.notifier = notifier;
            this.authorizationService = authorizationService;
            this.orchardServices = orchardServices;
            this.Logger = NullLogger.Instance;
            this.T = NullLocalizer.Instance;
        }

        public ILogger Logger { get; set; }

        public Localizer T { get; set; }

        public IEnumerable<PromotionRecord> GetPromotions()
        {
            return this.promotionsRepository.Table.ToList();
        }

        public PromotionRecord GetPromotion(int promotionId)
        {
            return this.promotionsRepository.Get(x => x.Id == promotionId);
        }

        public PromotionRecord GetPromotionByCode(string code)
        {
            return this.promotionsRepository.Get(x => x.PromotionCode == code);
        }

        public PromotionRecord CreatePromotion(string code, string description, string promotionType, double? discountRate, double? fixedAmountDiscount)
        {
            this.authorizationService.CheckAccess(Permissions.ManagePromotions, this.orchardServices.WorkContext.CurrentUser, null);

            var result = this.promotionsRepository.Get(x => x.PromotionCode == code);
            if (result == null)
            {
                result = new PromotionRecord { PromotionCode = code, Description = description, PromotionType = promotionType, DiscountRate = discountRate, FixedAmountDiscount = fixedAmountDiscount };
                this.promotionsRepository.Create(result);
            }

            return result;
        }

        public void DeletePromotion(int promotionId)
        {
            this.authorizationService.CheckAccess(Permissions.ManagePromotions, this.orchardServices.WorkContext.CurrentUser, null);

            var promotion = this.GetPromotion(promotionId);

            if (promotion == null) { return; }

            this.promotionsRepository.Delete(promotion);
        }

        public void UpdatePromotion(int promotionId, string code, string description, string promotionType, double? discountRate, double? fixedAmountDiscount)
        {
            this.authorizationService.CheckAccess(Permissions.ManagePromotions, this.orchardServices.WorkContext.CurrentUser, null);

            if (string.IsNullOrEmpty(code))
            {
                this.notifier.Warning(this.T("Couldn't update promotion: code was empty."));
                return;
            }

            // Verify other promotions codes does not match with the new one
            var promotionRecord = this.promotionsRepository.Get(p => p.PromotionCode == code && p.Id != promotionId);
            if (promotionRecord != null)
            {
                this.notifier.Warning(this.T("The promotion code specified already exists."));
                return;
            }

            var promotion = this.GetPromotion(promotionId);

            if (promotion == null)
            {
                this.notifier.Warning(this.T("Promotion doesn't exists."));
                return;
            }

            promotion.PromotionCode = code;
            promotion.Description = description;
            promotion.DiscountRate = discountRate;
            promotion.FixedAmountDiscount = fixedAmountDiscount;
            promotion.PromotionType = promotionType;

            this.promotionsRepository.Update(promotion);
        }

        public IEnumerable<TaxTableRecord> GetTaxTables()
        {
            return this.taxTablesRepository.Table.ToList();
        }

        public TaxTableRecord GetTaxTable(int taxTableId)
        {
            return this.taxTablesRepository.Get(x => x.Id == taxTableId);
        }

        public TaxTableRecord CreateTaxTable(string name, string taxes)
        {
            this.authorizationService.CheckAccess(Permissions.ManageTaxes, this.orchardServices.WorkContext.CurrentUser, null);

            var result = this.taxTablesRepository.Get(x => x.Name == name);
            if (result == null)
            {
                result = new TaxTableRecord { Name = name, Taxes = taxes };
                this.taxTablesRepository.Create(result);
            }

            return result;
        }

        public void DeleteTaxTable(int taxTableId)
        {
            var taxTable = this.taxTablesRepository.Get(x => x.Id == taxTableId);

            var taxes = (taxTable.Taxes ?? string.Empty).Split(',').Where(t => !string.IsNullOrEmpty(t.Trim()));

            if (taxes.Count() > 0)
            {
                foreach (var tax in taxes.Select(t => this.GetTaxRule(int.Parse(t))).Where(tax => tax != null))
                {
                    this.taxesRepository.Delete(tax);
                }
            }

            this.taxTablesRepository.Delete(taxTable);
        }

        public void UpdateTaxTable(int taxTableId, string name, string taxes)
        {
            this.authorizationService.CheckAccess(Permissions.ManageTaxes, this.orchardServices.WorkContext.CurrentUser, null);

            if (string.IsNullOrEmpty(name))
            {
                this.notifier.Warning(this.T("Couldn't update tax table: name was empty."));
                return;
            }

            var taxRecord = this.taxTablesRepository.Get(p => p.Name == name && p.Id != taxTableId);

            if (taxRecord != null)
            {
                this.notifier.Warning(this.T("The tax table name specified already exists."));
                return;
            }

            var taxTable = this.GetTaxTable(taxTableId);

            if (taxTable == null)
            {
                this.notifier.Warning(this.T("Tax table doesn't exists."));
                return;
            }

            taxTable.Name = name;
            taxTable.Taxes = taxes;

            this.taxTablesRepository.Update(taxTable);
        }

        public IEnumerable<TaxRuleRecord> GetTaxRules()
        {
            return this.taxesRepository.Table.ToList();
        }

        public IEnumerable<TaxRuleRecord> GetTaxRules(int taxtableId)
        {
            var taxTable = this.GetTaxTable(taxtableId);

            if (taxTable == null)
            {
                return null;
            }

            var taxes = (taxTable.Taxes ?? string.Empty).Split(',').Select(t => t.Trim());

            return this.taxesRepository.Table.ToList().Where(t => taxes.Contains(t.Id.ToString()));
        }

        public TaxRuleRecord GetTaxRule(int taxRuleId)
        {
            return this.taxesRepository.Get(t => t.Id == taxRuleId);
        }

        public TaxRuleRecord CreateTaxRule(string regionType, string region, decimal rate, bool shippingTaxed)
        {
            this.authorizationService.CheckAccess(Permissions.ManageTaxes, this.orchardServices.WorkContext.CurrentUser, null);

            var result = new TaxRuleRecord { RegionType = regionType, Region = region, Rate = rate, IsShippingTaxed = shippingTaxed };
            this.taxesRepository.Create(result);

            return result;
        }

        public void DeleteTaxRule(int taxRuleId)
        {
            this.authorizationService.CheckAccess(Permissions.ManageTaxes, this.orchardServices.WorkContext.CurrentUser, null);

            var tax = this.GetTaxRule(taxRuleId);

            if (tax == null) { return; }

            this.taxesRepository.Delete(tax);

            var taxTables = this.taxTablesRepository.Fetch(t => t.Taxes.Contains(taxRuleId.ToString()));

            foreach (var taxTable in taxTables)
            {
                var ids = taxTable.Taxes.Split(',').ToList().Where(i => !i.Trim().Equals(taxRuleId.ToString()));
                taxTable.Taxes = string.Join(",", ids);
                this.taxTablesRepository.Update(taxTable);
            }
        }

        public void UpdateTaxRule(int taxRuleId, string regionType, string region, decimal rate, bool shippingTaxed)
        {
            this.authorizationService.CheckAccess(Permissions.ManageTaxes, this.orchardServices.WorkContext.CurrentUser, null);

            if (string.IsNullOrEmpty(region))
            {
                this.notifier.Warning(this.T("Couldn't update promotion: region was empty."));
                return;
            }

            var tax = this.GetTaxRule(taxRuleId);

            if (tax == null)
            {
                this.notifier.Warning(this.T("Tax doesn't exists."));
                return;
            }

            tax.RegionType = regionType;
            tax.Region = region;
            tax.Rate = rate;
            tax.IsShippingTaxed = shippingTaxed;

            this.taxesRepository.Update(tax);
        }

        public IEnumerable<ShippingRecord> GetShippings()
        {
            return this.shippingRepository.Table.ToList();
        }

        public ShippingRecord CreateShipping(
                                                string includedRegionsString,
                                                string excludedRegionsString,
                                                string shipmentRateType,
                                                decimal shipmentBasedShippingRate,
                                                decimal weightBasedShippingRate,
                                                decimal itemQuantityBasedShippingRate,
                                                bool poboxSupported,
                                                string displayableShippingLabel,
                                                int serviceLevel)
        {
            this.authorizationService.CheckAccess(Permissions.ManageShippings, this.orchardServices.WorkContext.CurrentUser, null);

            var result = new ShippingRecord
            {
                DisplayableShippingLabel = displayableShippingLabel,
                ExcludedRegionsString = excludedRegionsString,
                IncludedRegionsString = includedRegionsString,
                ServiceLevel = serviceLevel,
                PoBoxSupported = poboxSupported,
                ShipmentRateType = shipmentRateType,
                ItemQuantityBasedShippingRate = itemQuantityBasedShippingRate,
                ShipmentBasedShippingRate = shipmentBasedShippingRate,
                WeightBasedShippingRate = weightBasedShippingRate
            };

            this.shippingRepository.Create(result);

            return result;
        }

        public void UpdateShipping(
                                    int shippingId,
                                    string includedRegionsString,
                                    string excludedRegionsString,
                                    string shipmentRateType,
                                    decimal shipmentBasedShippingRate,
                                    decimal weightBasedShippingRate,
                                    decimal itemQuantityBasedShippingRate,
                                    bool poboxSupported,
                                    string displayableShippingLabel,
                                    int serviceLevel)
        {
            this.authorizationService.CheckAccess(Permissions.ManageShippings, this.orchardServices.WorkContext.CurrentUser, null);

            var shipping = this.shippingRepository.Get(s => s.Id == shippingId);

            if (shipping != null)
            {
                shipping.DisplayableShippingLabel = displayableShippingLabel;
                shipping.ExcludedRegionsString = excludedRegionsString;
                shipping.IncludedRegionsString = includedRegionsString;
                shipping.ServiceLevel = serviceLevel;
                shipping.PoBoxSupported = poboxSupported;
                shipping.ShipmentRateType = shipmentRateType;
                shipping.ItemQuantityBasedShippingRate = itemQuantityBasedShippingRate;
                shipping.ShipmentBasedShippingRate = shipmentBasedShippingRate;
                shipping.WeightBasedShippingRate = weightBasedShippingRate;
            }
        }

        public ShippingRecord GetShipping(int shippingId)
        {
            return this.shippingRepository.Get(s => s.Id == shippingId);
        }

        public void DeleteShipping(int shippingId)
        {
            this.authorizationService.CheckAccess(Permissions.ManageShippings, this.orchardServices.WorkContext.CurrentUser, null);

            var shipping = this.shippingRepository.Get(s => s.Id == shippingId);

            if (shipping != null)
            {
                this.shippingRepository.Delete(shipping);
            }
        }
    }
}
